﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Collections;

using King.Declarative;
using King.Text;
using King.Extensions;
using King.CSharp.Reflection;

namespace King.CSharp.Decompiler {
#if false
    public abstract class DecompilerNode : LeafTextDeclaration {

        private HashSet<CSharpTypeSymbol> m_referencedTypes;
        internal DecompilerNode(HashSet<CSharpTypeSymbol> referencedTypes = null) {
            m_referencedTypes = referencedTypes;
        }
        internal HashSet<CSharpTypeSymbol> ReferencedTypes {
            get { return m_referencedTypes; }
        }
    }
    public abstract class DecompilerNodes : DecompilerNode, IEnumerable<object> {

        private List<object> m_objects;

        internal DecompilerNodes(HashSet<CSharpTypeSymbol> referencedTypes = null)
            : base(referencedTypes) {
            m_objects = new List<object>();
        }

        protected override IEnumerable Substance() {
            return Children();
        }
        protected IEnumerable Children() {
            return m_objects;
        }
        protected override IEnumerable Children(DeclarationContext context) {
            return Children();
        }

        public void Add(object element) {
            m_objects.Add(element);
        }
        IEnumerator<object> IEnumerable<object>.GetEnumerator() {
            throw new NotSupportedException("DecompilerNodes can only be enumerated during a traversal by TextGenerator.");
        }
        IEnumerator IEnumerable.GetEnumerator() {
            throw new NotSupportedException("DecompilerNodes can only be enumerated during a traversal by TextGenerator.");
        }
    }

    [Declaration(IsSubstantial = true)]
    internal abstract class ConstantDecompiler : LeafTextDeclaration {

        public static ConstantDecompiler Decompile(CSharpTypedConstant constant) {
            if (constant.Value == null)
                return new NullDecompiler(constant);

            if (constant.IsArray)
                return new ArrayDecompiler(constant);

            if (constant.IsEnum)
                return new EnumDecompiler(constant);

            if (constant.IsString)
                return new StringDecompiler(constant);

            if (constant.IsType)
                return new TypeofDecompiler(constant);

            if (constant.IsCharacter)
                return new CharDecompiler(constant);

            return new PrimitiveDecompiler(constant);
        }

        private CSharpTypedConstant m_constant;

        protected ConstantDecompiler(CSharpTypedConstant constant) {
            m_constant = constant;
        }

        protected CSharpTypedConstant Constant {
            get { return m_constant; }
        }
    }
    internal sealed class NullDecompiler : ConstantDecompiler {

        internal NullDecompiler(CSharpTypedConstant constant)
            : base(constant) {
        }

        protected override IEnumerable Children(DeclarationContext context) {
            yield return "null";
        }
    }
    internal sealed class CharDecompiler : ConstantDecompiler {

        internal CharDecompiler(CSharpTypedConstant constant)
            : base(constant) {
        }

        protected override IEnumerable Children(DeclarationContext context) {
            return new TextDeclaration() { '\'', Constant.Value, '\'' };
        }
    }
    internal sealed class StringDecompiler : ConstantDecompiler {

        internal StringDecompiler(CSharpTypedConstant constant)
            : base(constant) {
        }

        protected override IEnumerable Children(DeclarationContext context) {
            return new TextDeclaration() { '\"', Constant.Value, '\"' };
        }
    }
    internal sealed class PrimitiveDecompiler : ConstantDecompiler {

        internal PrimitiveDecompiler(CSharpTypedConstant constant)
            : base(constant) {
        }

        protected override IEnumerable Children(DeclarationContext context) {
            var value = Constant.Value;

            var suffix = "";
            var prefix = "";

            if (value is long) suffix = "L";
            else if (value is ulong) suffix = "uL";
            else if (value is double) suffix = "d";
            else if (value is float) suffix = "f";

            else if (value is sbyte) prefix = "(sbyte)";
            else if (value is short) prefix = "(short)";
            else if (value is byte) prefix = "(byte)";
            else if (value is ushort) prefix = "(ushort)";

            return new[] { prefix, Constant.Value, suffix};
        }
    }
    internal sealed class ArrayDecompiler : ConstantDecompiler {

        internal ArrayDecompiler(CSharpTypedConstant constant)
            : base(constant) {
        }

        protected override IEnumerable Children(DeclarationContext context) {
            var newExpression = "new[]";

            var values = Constant.Values;
            var elementTypes = values.GroupBy(o => o.Type);
            if (elementTypes.Count() != 1 || elementTypes.Single().Key.IsArray)
                newExpression = "new object[]";

            bool inline = values.Count() <= 3;

            return new TextJoin() { 
                newExpression, new BlockDecompiler(inline: inline) {
                    new TextJoin(", ") { Constant.Values.Select(o => Decompile(o)) }
                }
            };
        }
    }
    internal sealed class EnumDecompiler : ConstantDecompiler {

        internal EnumDecompiler(CSharpTypedConstant constant)
            : base(constant) {
        }

        protected override IEnumerable Children(DeclarationContext context) {
            return new TextJoin(" | ") {
                from value in Constant.Type.EnumValues(Constant.Value)
                select new TextDeclaration() { Constant.Type.Name, '.', value.Name }
            };
        }
    }
    internal sealed class TypeofDecompiler : ConstantDecompiler {

        internal TypeofDecompiler(CSharpTypedConstant constant)
            : base(constant) {
        }

        protected override IEnumerable Children(DeclarationContext context) {
            var type = (CSharpTypeSymbol)Constant.Value;
            var name = new TypeNameDecompiler(type);
            yield return new TextDeclaration() { "typeof(", name, ")" };
        }
    }

    [Declaration(IsSubstantial = true)]
    internal class AttributeDecompiler : LeafTextDeclaration {

        private CSharpAttributeSymbol m_attribute;

        public AttributeDecompiler(CSharpAttributeSymbol attribute) {
            m_attribute = attribute;
        }

        protected override IEnumerable Children(DeclarationContext context) {
            var name = m_attribute.Type.Name;

            // http://social.msdn.microsoft.com/Forums/en-US/roslyn/thread/b390d35a-998b-46ce-8ac1-db8e2af089d6
            //var name = Regex.Replace(m_attribute.Type.Name, "Attribute$", string.Empty);

            var arguments = m_attribute.Arguments().Select(o => ConstantDecompiler.Decompile(o));

            var namedArguments = 
                from o in m_attribute.NamedArguments()
                let field = o.Key
                let constant = o.Value
                select new KeyValuePair<object, object>(
                    field.Name, ConstantDecompiler.Decompile(constant)
                );

            yield return new CSharpAttribute(name, arguments, namedArguments);
        }
    }

    internal class AttributeGroupDecompiler : TextDeclaration {

        private static string GetTarget(CSharpAttributeSymbol attribute) {
            var symbol = attribute.DeclaringSymbol;
            if (symbol is CSharpAssemblySymbol)
                return "assembly";

            if (symbol is CSharpModuleSymbol)
                return "module";

            return null;
        }

        protected override IEnumerable Substance() {
            if (Children().Any())
                return Substantial;

            return null;
        }
        protected override IEnumerable Children(DeclarationContext context) {

            var result =
                from o in Children()
                group o by GetTarget((CSharpAttributeSymbol)o) into o
                select new CSharpAttributeGroup(o.Key) {
                    o.Select(x => new AttributeDecompiler((CSharpAttributeSymbol)x))
                };

            return result;
        }
    }

    [Declaration(IsSubstantial = true)]
    public sealed class AssemblyDecompiler : TextDeclaration {

        [Declaration(SubstituteTypes = typeof(CSharpAttributeSymbol))]
        private class AttributeSubstitute : TextDeclaration {

            protected override IEnumerable Substitute(DeclarationContext context, object value) {
                return new AttributeGroupDecompiler() { (CSharpAttributeSymbol)value };
            }
        }

        private CSharpAssemblySymbol m_assembly;
        private IEnumerable<object> m_attributes;

        public AssemblyDecompiler(CSharpAssemblySymbol assembly,
            IEnumerable<object> attributes) {

            m_assembly = assembly;
            m_attributes = attributes ?? m_assembly.Attributes().Union(m_assembly.Modules().SelectMany(o => o.Attributes()));
        }

        protected override IEnumerable Children(DeclarationContext context) {

            return new TextDoubleSpaced() {

                new AttributeSubstitute() { 
                    new TextSingleSpaced() {
                        m_attributes 
                    }
                },

                Children()
            };
        }
    }

    [Declaration(IsSubstantial = true)]
    public sealed class TypeNameDecompiler : LeafTextDeclaration {

        private CSharpTypeSymbol m_type;

        public TypeNameDecompiler(CSharpTypeSymbol type) {
            m_type = type;
        }

        protected override IEnumerable Children(DeclarationContext context) {
            
            var observer = context.Ancestors<TypeObserverDecompiler>().FirstOrDefault();
            if (observer != null)
                observer.Types.Add(m_type);

            var result = m_type.KeywordName ?? m_type.Name;

            if (m_type.IsGenericParameter) {
                yield return result;

            } else {
                //if (m_type.DeclaringType != null)
                //    result = new TypeNameDecompiler(m_type.DeclaringType) + "." + m_type.Name;

                //else if (m_type.Namespace != null)
                //    result = m_type.Namespace + "." + result;

                yield return result;

                if (m_type.IsGenericType)
                    yield return new TextJoin(", ", "<", ">") {
                        m_type.GenericArguments().Select(o => new TypeNameDecompiler(o))
                    };
            }
        }
    }

    [Declaration(IsSubstantial = true)]
    public sealed class TypeDecompiler : TextDeclaration {

        private CSharpTypeSymbol m_type;
        private IEnumerable<CSharpAttributeSymbol> m_attributes;
        private IEnumerable<CSharpTypeSymbol> m_interfaces;

        public TypeDecompiler(CSharpTypeSymbol type, 
            IEnumerable<CSharpAttributeSymbol> attributes = null, 
            IEnumerable<CSharpTypeSymbol> interfaces = null,
            HashSet<CSharpTypeSymbol> referencedTypes = null) {

            m_type = type;
            m_attributes = attributes ?? type.Attributes();
            m_interfaces = interfaces ?? type.Interfaces().OrderBy(o => o.AssemblyQualifiedName);
        }

        protected override IEnumerable Children(DeclarationContext context) {

            object baseTypeText = null;
            object interfacesText = null;

            if (m_type.IsClass || m_type.IsStruct || m_type.IsInterface) {

                if (m_type.BaseType != null && m_type.BaseType != typeof(object) && m_type.BaseType != typeof(ValueType))
                    baseTypeText = new TypeNameDecompiler(m_type.BaseType);

                //interfacesText = m_interfaces.Select(o => new TypeNameDecompiler(o));
            }

            var attributesText = new CSharpAttributeGroup() { 
                m_attributes.Select(o => new AttributeDecompiler(o)) 
            };

            var constraints = m_type.GenericArguments()
                .Select(o => new CSharpGenericParameterConstraint(
                    o.Name, o.GenericParameterConstraints().Where(x => x != typeof(ValueType)),
                    o.HasClassConstraint, o.HasStructConstraint, o.HasNewConstraint));

            yield return new CSharpType(new TypeNameDecompiler(m_type),
                baseType: baseTypeText,
                interfaces: interfacesText,
                attributes: attributesText,
                constraints: constraints,
                
                isPublic: m_type.IsPublic,
                isPrivate: m_type.IsPrivate,
                isProtected: m_type.IsProtected || m_type.IsProtectedInternal,
                isInternal: m_type.IsInternal || m_type.IsProtectedInternal,
                
                isStruct: m_type.IsStruct,
                isInterface: m_type.IsInterface,
                isEnum: m_type.IsEnum,

                isAbstract: m_type.IsClass && m_type.IsAbstract,
                isStatic: m_type.IsClass && m_type.IsStatic,
                isSealed: m_type.IsClass && m_type.IsSealed,
                isExtern: m_type.IsExtern) { };
        }
    }

    [Declaration(IsSubstantial = true)]
    public sealed class MethodDecompiler : TextDeclaration {
    }

    [Declaration(SubstituteTypes = typeof(CSharpSymbol))]
    public sealed class SymbolInfoDecompiler : TextDeclaration {

        protected override IEnumerable Substitute(DeclarationContext context, object value) {
            var type = value as CSharpTypeSymbol;
            if (type != null)
                return new TypeDecompiler(type);

            throw new NotImplementedException();
        }
    }

    internal sealed class BlockDecompiler : TextDeclaration {

        private bool m_openBraceOnNewLine;
        private bool m_inline;
        private bool m_force;

        public BlockDecompiler(
            bool openBraceOnNewLine = false,
            bool inline = true,
            bool force = true) {

            m_openBraceOnNewLine = openBraceOnNewLine;
            m_inline = inline;
            m_force = force;
        }

        protected override IEnumerable Substance() {
            if (m_force)
                return Substantial;
                
            return Children();
        }
        protected override IEnumerable Children(DeclarationContext context) {
            var containsContent = context.IsSubstantial(Children());
            var content = new TextSingleSpaced() { Children() };
            var inline = m_inline && !context.ContainsNewLine(content);

            if (inline) {
                yield return "{" + " ";

                if (containsContent) {
                    yield return Children();
                    yield return " ";
                }

                yield return "}";

            } else {
                if (m_openBraceOnNewLine)
                    yield return Environment.NewLine;

                yield return "{" + Environment.NewLine;

                if (containsContent) {
                    yield return new TextIndent() { 
                        content 
                    };
                    yield return Environment.NewLine;
                }

                yield return "}";
            }
        }
    }

    public sealed class TypeObserverDecompiler : TextDeclaration {
        private HashSet<CSharpTypeSymbol> m_types;

        public TypeObserverDecompiler(HashSet<CSharpTypeSymbol> types) {
            m_types = types;
        }

        public HashSet<CSharpTypeSymbol> Types {
            get { return m_types; }
        }
    }
#endif
}
